package credit.ccx.com.walletcore.util;

import android.content.res.AssetManager;
import android.util.Log;

import org.json.JSONArray;
import org.json.JSONException;
import org.spongycastle.crypto.digests.SHA512Digest;
import org.spongycastle.crypto.generators.PKCS5S2ParametersGenerator;
import org.spongycastle.crypto.params.KeyParameter;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import credit.ccx.com.walletcore.base.BaseApplication;

import static org.web3j.crypto.Hash.sha256;

/**
 * Created by gongdongyang on 2018/7/3.
 */

public class MyMnemonicUtils {
    private static final String TAG = "MyMnemonicUtils";
    private static final int SEED_ITERATIONS = 2048;
    private static final int SEED_KEY_SIZE = 512;
    private static final List<String> WORD_LIST = populateWordList();

    /**
     * The mnemonic must encode entropy in a multiple of 32 bits. With more entropy security is
     * improved but the sentence length increases. We refer to the initial entropy length as ENT.
     * The allowed size of ENT is 128-256 bits.
     *
     * <h3>Mnemonic generation algorithm</h3>
     * Given a randomly generated initial entropy of size ENT, first a checksum is generated by
     * taking the first {@code ENT / 32} bits of its SHA256 hash. This checksum is appended to
     * the end of the initial entropy. Next, these concatenated bits are split into groups of
     * 11 bits, each encoding a number from 0-2047, serving as an index into a wordlist. Finally,
     * we convert these numbers into words and use the joined words as a mnemonic sentence.
     *
     * @param initialEntropy The initial entropy to generate mnemonic from
     * @return The generated mnemonic
     * @throws IllegalArgumentException If the given entropy is invalid
     * *助记符必须以32位倍数编码熵。随着更多的熵安全

     *改进，但句子长度增加。我们把初始熵长度称为。

     *允许的大小的公司是128-256位。

     *

     *

     *给定一个随机生成的初始熵的大小ENT，第一个校验和是由

     *以第一{ @代码ENT / 32 }位的SHA256哈希。此校验和被追加到

     *初始熵的结束。接下来，这些级联的位被分成若干组。

     * 11位，每个编码数字0-2047，作为为一个词表索引。最后，

    我们把这些数字转换成单词，并用连接词作为助记句。

     *


     *返回生成的助记符

     * @抛出IllegalArgumentException如果给定的熵是无效的
     */
    public static String generateMnemonic(byte[] initialEntropy) {
        validateInitialEntropy(initialEntropy);

        int ent = initialEntropy.length * 8;
        int checksumLength = ent / 32;

        byte checksum = calculateChecksum(initialEntropy);
        boolean[] bits = convertToBits(initialEntropy, checksum);

        int iterations = (ent + checksumLength) / 11;
        StringBuilder mnemonicBuilder = new StringBuilder();
        for (int i = 0; i < iterations; i++) {
            int index = toInt(nextElevenBits(bits, i));
            mnemonicBuilder.append(WORD_LIST.get(index));

            boolean notLastIteration = i < iterations - 1;
            if (notLastIteration) {
                mnemonicBuilder.append(" ");
            }
        }
        return mnemonicBuilder.toString();
    }

    /**
     * To create a binary seed from the mnemonic, we use the PBKDF2 function with a
     * mnemonic sentence (in UTF-8 NFKD) used as the password and the string "mnemonic"
     * + passphrase (again in UTF-8 NFKD) used as the salt. The iteration count is set
     * to 2048 and HMAC-SHA512 is used as the pseudo-random function. The length of the
     * derived key is 512 bits (= 64 bytes).
     *
     * @param mnemonic The input mnemonic which should be 128-160 bits in length containing
     *                 only valid words
     * @param passphrase The passphrase which will be used as part of salt for PBKDF2
     *                   function
     * @return Byte array representation of the generated seed
     * *从记忆创建一个二进制的种子，我们使用PBKDF2功能有

     *记忆句子（UTF-8 nfkd）作为密码字符串“记忆”

     * +密码（再次UTF-8 nfkd）作为盐。迭代次数设置

     * 2048和hmac-sha512作为伪随机函数。的长度

     *导出密钥是512位（= 64字节）。

     *

     * @param记忆输入助记符应128-160比特长度包含

    只有有效的话

     * @param口令密码将作为部分盐PBKDF2

     *功能

     * @返回字节数组表示的生成的种子
     */
    public static byte[] generateSeed(String mnemonic, String passphrase) {
        validateMnemonic(mnemonic);
        passphrase = passphrase == null ? "" : passphrase;

        String salt = String.format("mnemonic%s", passphrase);
        PKCS5S2ParametersGenerator gen = new PKCS5S2ParametersGenerator(new SHA512Digest());
        gen.init(mnemonic.getBytes(Charset.forName("UTF-8")), salt.getBytes(Charset.forName("UTF-8")), SEED_ITERATIONS);

        return ((KeyParameter) gen.generateDerivedParameters(SEED_KEY_SIZE)).getKey();
    }

    private static void validateMnemonic(String mnemonic) {
        if (mnemonic == null || mnemonic.trim().isEmpty()) {
            throw new IllegalArgumentException("Mnemonic is required to generate a seed");
        }
    }

    private static boolean[] nextElevenBits(boolean[] bits, int i) {
        int from = i * 11;
        int to = from + 11;
        return Arrays.copyOfRange(bits, from, to);
    }

    private static void validateInitialEntropy(byte[] initialEntropy) {
        if (initialEntropy == null) {
            throw new IllegalArgumentException("Initial entropy is required");
        }

        int ent = initialEntropy.length * 8;
        if (ent < 128 || ent > 256 || ent % 32 != 0) {
            throw new IllegalArgumentException("The allowed size of ENT is 128-256 bits of "
                    + "multiples of 32");
        }
    }

    private static boolean[] convertToBits(byte[] initialEntropy, byte checksum) {
        int ent = initialEntropy.length * 8;
        int checksumLength = ent / 32;
        int totalLength = ent + checksumLength;
        boolean[] bits = new boolean[totalLength];

        for (int i = 0; i < initialEntropy.length; i++) {
            for (int j = 0; j < 8; j++) {
                byte b = initialEntropy[i];
                bits[8 * i + j] = toBit(b, j);
            }
        }

        for (int i = 0; i < checksumLength; i++) {
            bits[ent + i] = toBit(checksum, i);
        }

        return bits;
    }

    private static boolean toBit(byte value, int index) {
        return ((value >>> (7 - index)) & 1) > 0;
    }

    private static int toInt(boolean[] bits) {
        int value = 0;
        for (int i = 0; i < bits.length; i++) {
            boolean isSet = bits[i];
            if (isSet)  {
                value += 1 << bits.length - i - 1;
            }
        }

        return value;
    }

    private static byte calculateChecksum(byte[] initialEntropy) {
        int ent = initialEntropy.length * 8;
        byte mask = (byte) (0xff << 8 - ent / 32);
        byte[] bytes = sha256(initialEntropy);

        return (byte) (bytes[0] & mask);
    }

    private static List<String> populateWordList() {
        List<String> stringList = new ArrayList<>();
        String jsonString =  getJson("english.json");
        try {
            JSONArray array = new JSONArray(jsonString);
            Log.d(TAG, "array: "+array.toString());
            for (int i=0;i<array.length();i++){
//                stringList.add( array.get(i).toString().substring(1,array.get(i).toString().length()-1));
                stringList.add( array.get(i).toString());
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
        Log.d(TAG, "populateWordList: "+stringList.toString());


        return stringList;
//          ​try {
//            InputStream is = App.getInstance().getAssets().open("en-mnemonic-word-list.txt.txt");
//            ​int size = is.available();//取得数据流的数据大小
//            byte[] buffer = new byte[size];
//            is.read(buffer);
//            is.close();
//            String text = new String(buffer, "GB2312");
//        } catch (IOException e) {
//            e.printStackTrace();
//        }
//        URL url = Thread.currentThread().getContextClassLoader()
//                .getResource("en-mnemonic-word-list.txt");
//        try {
//            return Files.readAllLines(Paths.get(url.toURI()));
//        } catch (Exception e) {
//            return Collections.emptyList();
//        }
    }

    public static String getJson(String fileName) {
        StringBuilder stringBuilder = new StringBuilder();
        //获得assets资源管理器
        AssetManager assetManager = BaseApplication.application.getAssets();
        //使用IO流读取json文件内容
        try {
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
                    assetManager.open(fileName), "utf-8"));
            String line;
            while ((line = bufferedReader.readLine()) != null) {
                stringBuilder.append(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return stringBuilder.toString();
    }
}
